﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;

namespace FirstWebServer
{
    class Client
    {
        private Dictionary<string, string> extensionDict;

        private void addExtensionDict(TcpClient Client)
        {
            var doc = new XmlDocument();
            try
            {
                doc.Load("Context.xml");
            }
            catch (FileNotFoundException)
            {
                OnNotify("ERROR: Context.xml not found");
            }
            XmlNodeList resources = doc.SelectNodes("data/resource");
            extensionDict= new Dictionary<string,string>();
            foreach (XmlNode node in resources)
            {
                extensionDict.Add(node.Attributes["key"].Value, node.InnerText);
            }
        }

        protected static int clientQuantity = 0;

         public Client(TcpClient Client)
        {
            clientQuantity++;
            addExtensionDict(Client);
            byte[] Buffer; 
            string Request;
            int Count;
            OnNotify(string.Format("Client #{0} created:",clientQuantity));
            catchRequest(Client, out Request, out  Buffer, out  Count);
            parseRequest(Client, Request, Buffer, Count);
            Client.Close();
        }
        ~Client() 
        {
            OnNotify(string.Format("Client #{0} destroyed.", clientQuantity));
        }

        protected void OnNotify(string s)
        {
            if (Notify != null) Notify(s);
        }

        public delegate void Notify_EventHandler(string N);
        public static event Notify_EventHandler Notify;

        private void SendError(TcpClient Client, int Code)
        {
            string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString();
            string Html = "<html><body><h1>" + CodeStr + "</h1></body></html>";
            string Str = "HTTP/1.1 " + CodeStr + "\nContent-type: text/html\nContent-Length:" + Html.Length.ToString() + "\n\n" + Html;
            byte[] Buffer = Encoding.ASCII.GetBytes(Str);
            OnNotify("Error encountered: " + CodeStr);
            Client.GetStream().Write(Buffer, 0, Buffer.Length);
            Client.Close();
        }

        private void catchRequest(TcpClient Client, out string Request, out byte[] Buffer, out int Count)
        {
            Request = "";
            // Буфер для хранения принятых от клиента данных
            Buffer = new byte[1024];
            // Переменная для хранения количества байт, принятых от клиента
            //int Count;
            // Читаем из потока клиента до тех пор, пока от него поступают данные
            OnNotify("\nCatching request...\n{\n\n");
            while ((Count = Client.GetStream().Read(Buffer, 0, Buffer.Length)) > 0)
            {
                // Преобразуем эти данные в строку и добавим ее к переменной Request
                Request += Encoding.ASCII.GetString(Buffer, 0, Count);
                OnNotify(Encoding.ASCII.GetString(Buffer, 0, Count));
                // Запрос должен обрываться последовательностью \r\n\r\n
                // Либо обрываем прием данных сами, если длина строки Request превышает 4 килобайта
                // Нам не нужно получать данные из POST-запроса (и т. п.), а обычный запрос
                // по идее не должен быть больше 4 килобайт
                if (Request.IndexOf("\r\n\r\n") >= 0 || Request.Length > 4096)
                {
                    break;
                }
            }
            OnNotify("}\n");

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Client"></param>
        /// <param name="Request"></param>
        /// <param name="Buffer"></param>
        /// <param name="Count"></param>
        private void parseRequest(TcpClient Client, string Request, byte[] Buffer, int Count)
        {
           
            Match ReqMatch = Regex.Match(Request, @"^\w+\s+([^\s\?]+)[^\s]*\s+HTTP/.*|");

            // Если запрос не удался
            if (ReqMatch == Match.Empty)
            {
                // Передаем клиенту ошибку 400 - неверный запрос
                SendError(Client, 400);
                return;
            }

            // Получаем строку запроса
            string RequestUri = ReqMatch.Groups[1].Value;
            OnNotify("RequestUri: " + RequestUri);

            // Приводим ее к изначальному виду, преобразуя экранированные символы
            // Например, "%20" -> " "
            RequestUri = Uri.UnescapeDataString(RequestUri);

            // Это нужно для защиты от URL типа http://example.com/../../file.txt
            if (RequestUri.IndexOf("..") >= 0)
            {
                SendError(Client, 400);
                return;
            }

            if (RequestUri.EndsWith("/"))
            {
                RequestUri +=  "index.html";
            }
            string FilePath = "www/" + RequestUri;
            OnNotify("FilePath: " + FilePath);

            if (!File.Exists(FilePath))
            {
                SendError(Client, 404);
                return;
            }

            FileStream FS;
            try
            {
                FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            }
            catch (FileNotFoundException)
            {
                SendError(Client, 500);
                return;
            }

            string Extension = string.Empty;

            if (RequestUri.Contains(".")) Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));

            if ((Extension.Length > 1)&&!extensionDict.ContainsKey(Extension))
            {
                extensionDict.Add(Extension, string.Format("application/{0}", Extension.Substring(1)));
            }

            string Headers = "HTTP/1.1 200 OK\nContent-Type: " + extensionDict[Extension] + "\nContent-Length: " + FS.Length + "\n\n";
            OnNotify("Sending headers...\n{\n\n");
            OnNotify(Headers);
            OnNotify("}\n");


            byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
            Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);

            while (FS.Position < FS.Length)
            {
                Count = FS.Read(Buffer, 0, Buffer.Length);
                OnNotify("Sending file: " + FilePath);
                Client.GetStream().Write(Buffer, 0, Count);
            }
            FS.Close();
        }

    }
}
